/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#include "MeshData.h"

namespace PQEngine
{
	MeshData::MeshData()
	{
		_vertexBufferSize=0;
		_indexBufferSize=0;
		_vertexBuffer=0;
		_indexBuffer=0;
	};

	MeshData::~MeshData()
	{
		if(_vertexBuffer){
			OGRE_FREE(_vertexBuffer, Ogre::MEMCATEGORY_GEOMETRY);
		}
		if(_indexBuffer){
			OGRE_FREE(_indexBuffer, Ogre::MEMCATEGORY_GEOMETRY);
		}
	}

	void MeshData::loadMeshEntity(Ogre::Entity *entity,Ogre::Node *node)
	{
		loadMeshEntity(entity,node->_getDerivedPosition(),node->_getDerivedOrientation(),node->_getDerivedScale());
	}

	void MeshData::loadMeshEntity(Ogre::Entity *entity,Ogre::Vector3 position, Ogre::Quaternion orientation,Ogre::Vector3 scale)
	{
		_subMeshCount=0;
		if(!entity){
			return;
		}

		bool added_shared = false;
		int current_offset = 0;
		int shared_offset = 0;
		int next_offset = 0;
		int index_offset = 0;
		int vertex_count =0;
		int index_count = 0;
		
		Ogre::MeshPtr mesh=entity->getMesh();
		_subMeshCount=mesh->getNumSubMeshes();

		for (int i=0;i<_subMeshCount;i++){
			Ogre::SubMesh* submesh = mesh->getSubMesh(i);
			//add shared vertex only once
			if(submesh->useSharedVertices){
				if(!added_shared){
					vertex_count += mesh->sharedVertexData->vertexCount;
					added_shared = true;
				}
			}else{
				vertex_count += submesh->vertexData->vertexCount;
			}
			// Add the indices
			index_count += submesh->indexData->indexCount;
		}

		if(vertex_count > _vertexBufferSize){
			if(_vertexBuffer){
				OGRE_FREE(_vertexBuffer, Ogre::MEMCATEGORY_GEOMETRY);//mem alloc is time critical 
			}
			_vertexBuffer = OGRE_ALLOC_T(Ogre::Vector3,vertex_count, Ogre::MEMCATEGORY_GEOMETRY);
			_vertexBufferSize = vertex_count;
		}

		if(index_count > _indexBufferSize){
			if(_indexBuffer){
				OGRE_FREE(_indexBuffer, Ogre::MEMCATEGORY_GEOMETRY);
			}
			_indexBuffer = OGRE_ALLOC_T(unsigned long,index_count, Ogre::MEMCATEGORY_GEOMETRY);
			_indexBufferSize = index_count;
		}

		added_shared = false;
		// Run through the submeshes again, adding the data into the arrays
		for(int i = 0; i < _subMeshCount; i++){
			Ogre::SubMesh* submesh = mesh->getSubMesh(i);
			Ogre::VertexData* vertex_data = submesh->useSharedVertices ? mesh->sharedVertexData : submesh->vertexData;

			if((!submesh->useSharedVertices)||(submesh->useSharedVertices && !added_shared)){
				if(submesh->useSharedVertices){
				  added_shared = true;
				  shared_offset = current_offset;
				}

				const Ogre::VertexElement* posElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);
				Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(posElem->getSource());
				unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));

				// There is _no_ baseVertexPointerToElement() which takes an Ogre::Real or a double
				//  as second argument. So make it float, to avoid trouble when Ogre::Real will
				//  be comiled/typedefed as double:
				//      Ogre::Real* pReal;
				float* pReal;
				for(Ogre::uint j = 0; j < vertex_data->vertexCount; ++j, vertex += vbuf->getVertexSize()){
					posElem->baseVertexPointerToElement(vertex, &pReal);

					Ogre::Vector3 pt(pReal[0], pReal[1], pReal[2]);

					_vertexBuffer[current_offset + j] = (orientation * (pt * scale)) + position;//to world coord?
				}

				vbuf->unlock();
				next_offset += vertex_data->vertexCount;
			}


			Ogre::IndexData* index_data = submesh->indexData;
			size_t numTris = index_data->indexCount / 3;
			Ogre::HardwareIndexBufferSharedPtr ibuf = index_data->indexBuffer;

			bool use32bitindexes = (ibuf->getType() == Ogre::HardwareIndexBuffer::IT_32BIT);

			unsigned long*  pLong = static_cast<unsigned long*>(ibuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
			unsigned short* pShort = reinterpret_cast<unsigned short*>(pLong);

			size_t offset = (submesh->useSharedVertices)? shared_offset : current_offset;

			if ( use32bitindexes ){
				for ( size_t k = 0; k < numTris*3; ++k){
					_indexBuffer[index_offset++] = pLong[k] + static_cast<unsigned long>(offset);
				}
			}else{
				for ( size_t k = 0; k < numTris*3; ++k){
					_indexBuffer[index_offset++] = static_cast<unsigned long>(pShort[k]) + static_cast<unsigned long>(offset);
				}
			}

			ibuf->unlock();
			current_offset = next_offset;
		}
		index_count = index_offset;
		
		_vertexCount = vertex_count;
		_indexCount= index_count;

	}

	
	bool MeshData::intersect(Ogre::Ray ray)
	{
		Ogre::Real closest_distance=-1.0f;

		for(int i = 0; i < _indexCount; i += 3){
			std::pair<bool, Ogre::Real> hitRet = Ogre::Math::intersects(ray, _vertexBuffer[_indexBuffer[i]],
				_vertexBuffer[_indexBuffer[i+1]], _vertexBuffer[_indexBuffer[i+2]], true, true);
            if (hitRet.first){
                // this is the closest so far, save it off
                closest_distance = hitRet.second;
				return true;
            }
        }
		return false;
	}
}
